<!DOCTYPE html>
<style>
h1 {
  font-family: sans-serif;
}
#postWarning {
  color: red;
}
textarea {
  width: calc(100%% - 10px);
  height: 50vh;
}
.item {
  font-weight: bold;
}
.item:before {
  content: '>>> ';
}
</style>

<h1>Chromium CQ Status</h1>
<pre>
Source code:
<a href="https://chromium.googlesource.com/infra/infra/+/master/appengine/chromium_cq_status/">https://chromium.googlesource.com/infra/infra/+/master/appengine/chromium_cq_status/</a>
Use "<a href="http://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/depot_tools_tutorial.html">fetch</a> infra" to clone the dev repos.



Site directory listing:

<span class="item"><a href="/recent">/recent</a></span>
Example: <a href="/recent#project=chromium,action=patch_start">/recent#project=chromium,action=patch_start</a>
Shows a table of recently received records.
Tags to filter by are specified as a comma separated list after the #.


<span class="item">/patch-status/&lt;issue&gt;/&lt;patchset&gt;</span>
Example: <a href="/patch-status/909033002/1">/patch-status/909033002/1</a>
Shows a timeline of CQ activity on a given patch.
Only accepts GET requests.


<span class="item">/patch-summary/&lt;issue&gt;/&lt;patchset&gt;</span>
Example: <a href="/patch-summary/909033002/1">/patch-summary/909033002/1</a>
API for getting a summary of the activity on a patch.
Only accepts GET requests.
Patch summary format (all duration values are in seconds):
{
  "success": bool,
  "begin": timestamp,
  "end": timestamp,
  "durations": &lt;durations summary&gt;,
  "job_counts": {
    "passed": int,
    "failed": int,
    "running": int
  },
  "flaky_jobs": [&lt;job summary&gt;],
  "attempt_count": int,
  "attempt_fail_count": int,
  "attempts": [{
    "success": bool,
    "fail_reason": json,
    "begin": timestamp,
    "end": timestamp,
    "durations": &lt;durations summary&gt;,
    "jobs": {
      "passed": [&lt;job summary&gt;],
      "failed": [&lt;job summary&gt;],
      "running": [&lt;job summary&gt;]
    }
  }]
}

Durations summary format:
{
  "running_all_jobs": float,
  "blocked_on_closed_tree": float,
  "blocked_on_throttled_tree": float,
  "committing": float,
  "total": float
}

Job summary format:
{
  "success": bool,
  "retry": bool,
  "begin": timestamp,
  "end": timestamp,
  "duration": float,
  "master": string,
  "builder": string,
  "slave": string,
  "buildnumber": int,
  "url": string
}


<span class="item">/stats/&lt;project&gt;/(15-minutely|hourly|daily|weekly)</span>
Examples:
<a href="/stats/chromium/15-minutely">/stats/chromium/15-minutely</a>
<a href="/stats/chromium/hourly">/stats/chromium/hourly</a>
<a href="/stats/chromium/daily">/stats/chromium/daily</a>
<a href="/stats/chromium/weekly">/stats/chromium/weekly</a>
<a href="/stats/blink/15-minutely">/stats/blink/15-minutely</a>
<a href="/stats/blink/hourly">/stats/blink/hourly</a>
<a href="/stats/blink/daily">/stats/blink/daily</a>
<a href="/stats/blink/weekly">/stats/blink/weekly</a>

Recent CQ stats for a project aggregated by various time intervals.
Only accepts GET requests.
Accepts optional URL param for how many data points to fetch:
  data_points: int


<span class="item"><a href="/stats/query">/stats/query</a></span>
Examples:
<a href="/stats/query?project=chromium&names=issue-count,patchset-*&count=3">/stats/query?project=chromium&names=issue-count,patchset-*&count=3</a>
<a href="/stats/query?key=6361816489787392&names=attempt-durations">/stats/query?key=6361816489787392&names=attempt-durations</a>

API for querying CQ stats.
Only accepts GET requests.
Accepts the following optional URL params:
  key: int
  project: str
  interval_minutes: int
  begin: timestamp
  end: timestamp
  names: str // Comma separated globs where * and ? are permitted for fuzzy matching.
  count: int=100 // Max 1000.
  cursor: str=''

The returned query results are in the following JSON structure:
{
  "results": [
    {
      "key": int,
      "project": str,
      "interval_minutes": int,
      "begin": timestamp,
      "end": timestamp,
      "stats": [
        {
          "type": "count",
          "name": str,
          "description": str,
          "count": int
        }
        OR
        {
          "type": "list",
          "name": str,
          "description": str,
          "unit": str,
          "sample_size": int,
          "min": float,
          "max": float,
          "mean": float,
          "percentile_10": float,
          "percentile_25": float,
          "percentile_50": float,
          "percentile_75": float,
          "percentile_90": float,
          "percentile_95": float,
          "percentile_99": float
        }
      ]
    }
  ],
  "cursor": str,
  "more": bool
}


<span class="item">/stats/(highest|lowest)/&lt;list stats name&gt;/&lt;CQ stats key&gt;</span>
Examples:
<a href="/stats/highest/attempt-durations/6361816489787392">/stats/highest/attempt-durations/6361816489787392</a>
<a href="/stats/lowest/attempt-durations/6361816489787392">/stats/lowest/attempt-durations/6361816489787392</a>

API for querying highest/lowest data points in individual stats.
Only accepts GET requests.
Data points are returned in the following JSON structure:
[
  [
    float,
    {data point metadata}
  ]
]
The highest data points are given in descending order.
The lowest data points are given in ascending order.
Data point metadata is typically of the form:
{
  "issue": int,
  "patchset": int
}


<span class="item"><a href="/query">/query</a></span>
Examples:
<a href="/query/project=chromium/action=patch_start?count=10">/query/project=chromium/action=patch_start?count=10</a>
<a href="/query?tags=project%%3Dchromium,action%%3Dpatch_start&count=10">/query?tags=project%%3Dchromium,action%%3Dpatch_start&count=10</a>

API for querying CQ records.
Only accepts GET requests.
Accepts the following optional URL params:
  key: str (non numeric)
  begin: timestamp
  end: timestamp
  tags: str (comma separated)
  fields: json
  count: int=100 (max 1000)
  cursor: str

The returned query results are in the following JSON structure:
{
  "results": [{
    "timestamp": int,
    "key": str|null,
    "tags": [str],
    "fields": {field: value}
  }],
  "more": bool,
  "cursor": str
}

Tags may be specified directly in the URL e.g. /query/tag_a/tag_b.


<span class="item">/post</span>
API for posting CQ records.
Requires @chromium.org/@google.com <a href="%(login_url)s">sign in</a>.
Only accepts POST requests.
Accepts multiple p URL params.
Each p value is a record to add in the following JSON structure:
{
  "key": str|null,
  "tags": [str],
  "fields": {field: value}
}
"project" is a required key in "fields".



TESTING ONLY:
<form id="queryForm">
Query entries from /query:
  key: <input type="textbox" name="key">
  begin: <input type="textbox" name="begin">
  end: <input type="textbox" name="end">
  tags: <input type="textbox" name="tags">
  fields: <input type="textbox" name="fields">
  count: <input type="textbox" name="count">
  cursor: <input type="textbox" name="cursor">
  <input type="reset" value="Clear"> <input type="button" id="querySubmit" value="Query">
</form>
Query view:
<textarea id="queryResult"></textarea>

<form id="postForm">
Post entry to /post:
  key: <input type="textbox" name="key">
  tags: <input type="textbox" name="tags">
  fields: <input type="textbox" name="fields">
  <input type="reset" value="Clear"> <input type="button" id="postSubmit" value="Post"> <span id="postWarning"></span>
Post result:
<textarea id="postResult"></textarea>
</form>
</pre>

<script>
if (!%(valid_user)d) {
  postWarning.textContent = 'Posting restricted to @chromium.org/@google.com users.';
  postSubmit.disabled = true;
}

function encodeForm(form) {
  return [].map.call(form.querySelectorAll('input[type=textbox]'), function(field) {
    return field.name + '=' + encodeURIComponent(field.value);
  }).join('&');
}

function loadResult(url, textarea) {
  textarea.value = '';
  var xhr = new XMLHttpRequest();
  xhr.open('get', url, true);
  xhr.onreadystatechange = function() {
    if (xhr.readyState === XMLHttpRequest.DONE) {
      try {
        var json = JSON.parse(xhr.responseText);
        textarea.value = JSON.stringify(json, null, '  ')
      } catch (_) {
        textarea.value = xhr.responseText;
      }
    }
  }
  xhr.send();
}

postSubmit.addEventListener('click', function() {
  loadResult('/post?' + encodeForm(postForm), postResult);
});
querySubmit.addEventListener('click', function() {
  loadResult('/query?' + encodeForm(queryForm), queryResult);
});
</script>
